1   /*
2    * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package com.sun.media.sound;
27  
28  import java.io.ByteArrayOutputStream;
29  import java.io.ByteArrayInputStream;
30  import java.io.DataOutputStream;
31  import java.io.IOException;
32  import java.io.InputStream;
33  
34  import java.util.ArrayList;
35  import java.util.List;
36  
37  import javax.sound.midi.*;
38  
39  
40  /**
41   * A Real Time Sequencer
42   *
43   * @author Florian Bomers
44   */
45  
46  /* TODO:
47   * - rename PlayThread to PlayEngine (because isn't a thread)
48   */
49  class RealTimeSequencer extends AbstractMidiDevice implements Sequencer, AutoConnectSequencer {
50  
51      // STATIC VARIABLES
52  
53      /** debugging flags */
54      private final static boolean DEBUG_PUMP = false;
55      private final static boolean DEBUG_PUMP_ALL = false;
56  
57      /**
58       * Event Dispatcher thread. Should be using a shared event
59       * dispatcher instance with a factory in EventDispatcher
60       */
61      private static final EventDispatcher eventDispatcher;
62  
63      /**
64       * All RealTimeSequencers share this info object.
65       */
66      static final RealTimeSequencerInfo info = new RealTimeSequencerInfo();
67  
68  
69      private static Sequencer.SyncMode[] masterSyncModes = { Sequencer.SyncMode.INTERNAL_CLOCK };
70      private static Sequencer.SyncMode[] slaveSyncModes  = { Sequencer.SyncMode.NO_SYNC };
71  
72      private static Sequencer.SyncMode masterSyncMode    = Sequencer.SyncMode.INTERNAL_CLOCK;
73      private static Sequencer.SyncMode slaveSyncMode     = Sequencer.SyncMode.NO_SYNC;
74  
75  
76      /**
77       * Sequence on which this sequencer is operating.
78       */
79      private Sequence sequence = null;
80  
81      // caches
82  
83      /**
84       * Same for setTempoInMPQ...
85       * -1 means not set.
86       */
87      private double cacheTempoMPQ = -1;
88  
89  
90      /**
91       * cache value for tempo factor until sequence is set
92       * -1 means not set.
93       */
94      private float cacheTempoFactor = -1;
95  
96  
97      /** if a particular track is muted */
98      private boolean[] trackMuted = null;
99      /** if a particular track is solo */
100     private boolean[] trackSolo = null;
101 
102     /** tempo cache for getMicrosecondPosition */
103     private MidiUtils.TempoCache tempoCache = new MidiUtils.TempoCache();
104 
105     /**
106      * True if the sequence is running.
107      */
108     private boolean running = false;
109 
110 
111     /** the thread for pushing out the MIDI messages */
112     private PlayThread playThread;
113 
114 
115     /**
116      * True if we are recording
117      */
118     private boolean recording = false;
119 
120 
121     /**
122      * List of tracks to which we're recording
123      */
124     private List recordingTracks = new ArrayList();
125 
126 
127     private long loopStart = 0;
128     private long loopEnd = -1;
129     private int loopCount = 0;
130 
131 
132     /**
133      * Meta event listeners
134      */
135     private ArrayList metaEventListeners = new ArrayList();
136 
137 
138     /**
139      * Control change listeners
140      */
141     private ArrayList controllerEventListeners = new ArrayList();
142 
143 
144     /** automatic connection support */
145     private boolean autoConnect = false;
146 
147     /** if we need to autoconnect at next open */
148     private boolean doAutoConnectAtNextOpen = false;
149 
150     /** the receiver that this device is auto-connected to */
151     Receiver autoConnectedReceiver = null;
152 
153 
154     static {
155         // create and start the global event thread
156         eventDispatcher = new EventDispatcher();
157         eventDispatcher.start();
158     }
159 
160 
161     /* ****************************** CONSTRUCTOR ****************************** */
162 
163     protected RealTimeSequencer() throws MidiUnavailableException {
164         super(info);
165 
166         if (Printer.trace) Printer.trace(">> RealTimeSequencer CONSTRUCTOR");
167         if (Printer.trace) Printer.trace("<< RealTimeSequencer CONSTRUCTOR completed");
168     }
169 
170 
171     /* ****************************** SEQUENCER METHODS ******************** */
172 
173     public synchronized void setSequence(Sequence sequence)
174         throws InvalidMidiDataException {
175 
176         if (Printer.trace) Printer.trace(">> RealTimeSequencer: setSequence(" + sequence +")");
177 
178         if (sequence != this.sequence) {
179             if (this.sequence != null && sequence == null) {
180                 setCaches();
181                 stop();
182                 // initialize some non-cached values
183                 trackMuted = null;
184                 trackSolo = null;
185                 loopStart = 0;
186                 loopEnd = -1;
187                 loopCount = 0;
188                 if (getDataPump() != null) {
189                     getDataPump().setTickPos(0);
190                     getDataPump().resetLoopCount();
191                 }
192             }
193 
194             if (playThread != null) {
195                 playThread.setSequence(sequence);
196             }
197 
198             // store this sequence (do not copy - we want to give the possibility
199             // of modifying the sequence at runtime)
200             this.sequence = sequence;
201 
202             if (sequence != null) {
203                 tempoCache.refresh(sequence);
204                 // rewind to the beginning
205                 setTickPosition(0);
206                 // propagate caches
207                 propagateCaches();
208             }
209         }
210         else if (sequence != null) {
211             tempoCache.refresh(sequence);
212             if (playThread != null) {
213                 playThread.setSequence(sequence);
214             }
215         }
216 
217         if (Printer.trace) Printer.trace("<< RealTimeSequencer: setSequence(" + sequence +") completed");
218     }
219 
220 
221     public synchronized void setSequence(InputStream stream) throws IOException, InvalidMidiDataException {
222 
223         if (Printer.trace) Printer.trace(">> RealTimeSequencer: setSequence(" + stream +")");
224 
225         if (stream == null) {
226             setSequence((Sequence) null);
227             return;
228         }
229 
230         Sequence seq = MidiSystem.getSequence(stream); // can throw IOException, InvalidMidiDataException
231 
232         setSequence(seq);
233 
234         if (Printer.trace) Printer.trace("<< RealTimeSequencer: setSequence(" + stream +") completed");
235 
236     }
237 
238 
239     public Sequence getSequence() {
240         return sequence;
241     }
242 
243 
244     public synchronized void start() {
245         if (Printer.trace) Printer.trace(">> RealTimeSequencer: start()");
246 
247         // sequencer not open: throw an exception
248         if (!isOpen()) {
249             throw new IllegalStateException("sequencer not open");
250         }
251 
252         // sequence not available: throw an exception
253         if (sequence == null) {
254             throw new IllegalStateException("sequence not set");
255         }
256 
257         // already running: return quietly
258         if (running == true) {
259             return;
260         }
261 
262         // start playback
263         implStart();
264 
265         if (Printer.trace) Printer.trace("<< RealTimeSequencer: start() completed");
266     }
267 
268 
269     public synchronized void stop() {
270         if (Printer.trace) Printer.trace(">> RealTimeSequencer: stop()");
271 
272         if (!isOpen()) {
273             throw new IllegalStateException("sequencer not open");
274         }
275         stopRecording();
276 
277         // not running; just return
278         if (running == false) {
279             if (Printer.trace) Printer.trace("<< RealTimeSequencer: stop() not running!");
280             return;
281         }
282 
283         // stop playback
284         implStop();
285 
286         if (Printer.trace) Printer.trace("<< RealTimeSequencer: stop() completed");
287     }
288 
289 
290     public boolean isRunning() {
291         return running;
292     }
293 
294 
295     public void startRecording() {
296         if (!isOpen()) {
297             throw new IllegalStateException("Sequencer not open");
298         }
299 
300         start();
301         recording = true;
302     }
303 
304 
305     public void stopRecording() {
306         if (!isOpen()) {
307             throw new IllegalStateException("Sequencer not open");
308         }
309         recording = false;
310     }
311 
312 
313     public boolean isRecording() {
314         return recording;
315     }
316 
317 
318     public void recordEnable(Track track, int channel) {
319         if (!findTrack(track)) {
320             throw new IllegalArgumentException("Track does not exist in the current sequence");
321         }
322 
323         synchronized(recordingTracks) {
324             RecordingTrack rc = RecordingTrack.get(recordingTracks, track);
325             if (rc != null) {
326                 rc.channel = channel;
327             } else {
328                 recordingTracks.add(new RecordingTrack(track, channel));
329             }
330         }
331 
332     }
333 
334 
335     public void recordDisable(Track track) {
336         synchronized(recordingTracks) {
337             RecordingTrack rc = RecordingTrack.get(recordingTracks, track);
338             if (rc != null) {
339                 recordingTracks.remove(rc);
340             }
341         }
342 
343     }
344 
345 
346     private boolean findTrack(Track track) {
347         boolean found = false;
348         if (sequence != null) {
349             Track[] tracks = sequence.getTracks();
350             for (int i = 0; i < tracks.length; i++) {
351                 if (track == tracks[i]) {
352                     found = true;
353                     break;
354                 }
355             }
356         }
357         return found;
358     }
359 
360 
361     public float getTempoInBPM() {
362         if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoInBPM() ");
363 
364         return (float) MidiUtils.convertTempo(getTempoInMPQ());
365     }
366 
367 
368     public void setTempoInBPM(float bpm) {
369         if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoInBPM() ");
370         if (bpm <= 0) {
371             // should throw IllegalArgumentException
372             bpm = 1.0f;
373         }
374 
375         setTempoInMPQ((float) MidiUtils.convertTempo((double) bpm));
376     }
377 
378 
379     public float getTempoInMPQ() {
380         if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoInMPQ() ");
381 
382         if (needCaching()) {
383             // if the sequencer is closed, return cached value
384             if (cacheTempoMPQ != -1) {
385                 return (float) cacheTempoMPQ;
386             }
387             // if sequence is set, return current tempo
388             if (sequence != null) {
389                 return tempoCache.getTempoMPQAt(getTickPosition());
390             }
391 
392             // last resort: return a standard tempo: 120bpm
393             return (float) MidiUtils.DEFAULT_TEMPO_MPQ;
394         }
395         return (float)getDataPump().getTempoMPQ();
396     }
397 
398 
399     public void setTempoInMPQ(float mpq) {
400         if (mpq <= 0) {
401             // should throw IllegalArgumentException
402             mpq = 1.0f;
403         }
404 
405         if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoInMPQ() ");
406 
407         if (needCaching()) {
408             // cache the value
409             cacheTempoMPQ = mpq;
410         } else {
411             // set the native tempo in MPQ
412             getDataPump().setTempoMPQ(mpq);
413 
414             // reset the tempoInBPM and tempoInMPQ values so we won't use them again
415             cacheTempoMPQ = -1;
416         }
417     }
418 
419 
420     public void setTempoFactor(float factor) {
421         if (factor <= 0) {
422             // should throw IllegalArgumentException
423             return;
424         }
425 
426         if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoFactor() ");
427 
428         if (needCaching()) {
429             cacheTempoFactor = factor;
430         } else {
431             getDataPump().setTempoFactor(factor);
432             // don't need cache anymore
433             cacheTempoFactor = -1;
434         }
435     }
436 
437 
438     public float getTempoFactor() {
439         if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoFactor() ");
440 
441         if (needCaching()) {
442             if (cacheTempoFactor != -1) {
443                 return cacheTempoFactor;
444             }
445             return 1.0f;
446         }
447         return getDataPump().getTempoFactor();
448     }
449 
450 
451     public long getTickLength() {
452         if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTickLength() ");
453 
454         if (sequence == null) {
455             return 0;
456         }
457 
458         return sequence.getTickLength();
459     }
460 
461 
462     public synchronized long getTickPosition() {
463         if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTickPosition() ");
464 
465         if (getDataPump() == null || sequence == null) {
466             return 0;
467         }
468 
469         return getDataPump().getTickPos();
470     }
471 
472 
473     public synchronized void setTickPosition(long tick) {
474         if (tick < 0) {
475             // should throw IllegalArgumentException
476             return;
477         }
478 
479         if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTickPosition("+tick+") ");
480 
481         if (getDataPump() == null) {
482             if (tick != 0) {
483                 // throw new InvalidStateException("cannot set position in closed state");
484             }
485         }
486         else if (sequence == null) {
487             if (tick != 0) {
488                 // throw new InvalidStateException("cannot set position if sequence is not set");
489             }
490         } else {
491             getDataPump().setTickPos(tick);
492         }
493     }
494 
495 
496     public long getMicrosecondLength() {
497         if (Printer.trace) Printer.trace(">> RealTimeSequencer: getMicrosecondLength() ");
498 
499         if (sequence == null) {
500             return 0;
501         }
502 
503         return sequence.getMicrosecondLength();
504     }
505 
506 
507     public long getMicrosecondPosition() {
508         if (Printer.trace) Printer.trace(">> RealTimeSequencer: getMicrosecondPosition() ");
509 
510         if (getDataPump() == null || sequence == null) {
511             return 0;
512         }
513         synchronized (tempoCache) {
514             return MidiUtils.tick2microsecond(sequence, getDataPump().getTickPos(), tempoCache);
515         }
516     }
517 
518 
519     public void setMicrosecondPosition(long microseconds) {
520         if (microseconds < 0) {
521             // should throw IllegalArgumentException
522             return;
523         }
524 
525         if (Printer.trace) Printer.trace(">> RealTimeSequencer: setMicrosecondPosition("+microseconds+") ");
526 
527         if (getDataPump() == null) {
528             if (microseconds != 0) {
529                 // throw new InvalidStateException("cannot set position in closed state");
530             }
531         }
532         else if (sequence == null) {
533             if (microseconds != 0) {
534                 // throw new InvalidStateException("cannot set position if sequence is not set");
535             }
536         } else {
537             synchronized(tempoCache) {
538                 setTickPosition(MidiUtils.microsecond2tick(sequence, microseconds, tempoCache));
539             }
540         }
541     }
542 
543 
544     public void setMasterSyncMode(Sequencer.SyncMode sync) {
545         // not supported
546     }
547 
548 
549     public Sequencer.SyncMode getMasterSyncMode() {
550         return masterSyncMode;
551     }
552 
553 
554     public Sequencer.SyncMode[] getMasterSyncModes() {
555         Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[masterSyncModes.length];
556         System.arraycopy(masterSyncModes, 0, returnedModes, 0, masterSyncModes.length);
557         return returnedModes;
558     }
559 
560 
561     public void setSlaveSyncMode(Sequencer.SyncMode sync) {
562         // not supported
563     }
564 
565 
566     public Sequencer.SyncMode getSlaveSyncMode() {
567         return slaveSyncMode;
568     }
569 
570 
571     public Sequencer.SyncMode[] getSlaveSyncModes() {
572         Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[slaveSyncModes.length];
573         System.arraycopy(slaveSyncModes, 0, returnedModes, 0, slaveSyncModes.length);
574         return returnedModes;
575     }
576 
577     protected int getTrackCount() {
578         Sequence seq = getSequence();
579         if (seq != null) {
580             // $$fb wish there was a nicer way to get the number of tracks...
581             return sequence.getTracks().length;
582         }
583         return 0;
584     }
585 
586 
587 
588     public synchronized void setTrackMute(int track, boolean mute) {
589         int trackCount = getTrackCount();
590         if (track < 0 || track >= getTrackCount()) return;
591         trackMuted = ensureBoolArraySize(trackMuted, trackCount);
592         trackMuted[track] = mute;
593         if (getDataPump() != null) {
594             getDataPump().muteSoloChanged();
595         }
596     }
597 
598 
599     public synchronized boolean getTrackMute(int track) {
600         if (track < 0 || track >= getTrackCount()) return false;
601         if (trackMuted == null || trackMuted.length <= track) return false;
602         return trackMuted[track];
603     }
604 
605 
606     public synchronized void setTrackSolo(int track, boolean solo) {
607         int trackCount = getTrackCount();
608         if (track < 0 || track >= getTrackCount()) return;
609         trackSolo = ensureBoolArraySize(trackSolo, trackCount);
610         trackSolo[track] = solo;
611         if (getDataPump() != null) {
612             getDataPump().muteSoloChanged();
613         }
614     }
615 
616 
617     public synchronized boolean getTrackSolo(int track) {
618         if (track < 0 || track >= getTrackCount()) return false;
619         if (trackSolo == null || trackSolo.length <= track) return false;
620         return trackSolo[track];
621     }
622 
623 
624     public boolean addMetaEventListener(MetaEventListener listener) {
625         synchronized(metaEventListeners) {
626             if (! metaEventListeners.contains(listener)) {
627 
628                 metaEventListeners.add(listener);
629             }
630             return true;
631         }
632     }
633 
634 
635     public void removeMetaEventListener(MetaEventListener listener) {
636         synchronized(metaEventListeners) {
637             int index = metaEventListeners.indexOf(listener);
638             if (index >= 0) {
639                 metaEventListeners.remove(index);
640             }
641         }
642     }
643 
644 
645     public int[] addControllerEventListener(ControllerEventListener listener, int[] controllers) {
646         synchronized(controllerEventListeners) {
647 
648             // first find the listener.  if we have one, add the controllers
649             // if not, create a new element for it.
650             ControllerListElement cve = null;
651             boolean flag = false;
652             for(int i=0; i < controllerEventListeners.size(); i++) {
653 
654                 cve = (ControllerListElement) controllerEventListeners.get(i);
655 
656                 if (cve.listener.equals(listener)) {
657                     cve.addControllers(controllers);
658                     flag = true;
659                     break;
660                 }
661             }
662             if (!flag) {
663                 cve = new ControllerListElement(listener, controllers);
664                 controllerEventListeners.add(cve);
665             }
666 
667             // and return all the controllers this listener is interested in
668             return cve.getControllers();
669         }
670     }
671 
672 
673     public int[] removeControllerEventListener(ControllerEventListener listener, int[] controllers) {
674         synchronized(controllerEventListeners) {
675             ControllerListElement cve = null;
676             boolean flag = false;
677             for (int i=0; i < controllerEventListeners.size(); i++) {
678                 cve = (ControllerListElement) controllerEventListeners.get(i);
679                 if (cve.listener.equals(listener)) {
680                     cve.removeControllers(controllers);
681                     flag = true;
682                     break;
683                 }
684             }
685             if (!flag) {
686                 return new int[0];
687             }
688             if (controllers == null) {
689                 int index = controllerEventListeners.indexOf(cve);
690                 if (index >= 0) {
691                     controllerEventListeners.remove(index);
692                 }
693                 return new int[0];
694             }
695             return cve.getControllers();
696         }
697     }
698 
699 
700     ////////////////// LOOPING (added in 1.5) ///////////////////////
701 
702     public void setLoopStartPoint(long tick) {
703         if ((tick > getTickLength())
704             || ((loopEnd != -1) && (tick > loopEnd))
705             || (tick < 0)) {
706             throw new IllegalArgumentException("invalid loop start point: "+tick);
707         }
708         loopStart = tick;
709     }
710 
711     public long getLoopStartPoint() {
712         return loopStart;
713     }
714 
715     public void setLoopEndPoint(long tick) {
716         if ((tick > getTickLength())
717             || ((loopStart > tick) && (tick != -1))
718             || (tick < -1)) {
719             throw new IllegalArgumentException("invalid loop end point: "+tick);
720         }
721         loopEnd = tick;
722     }
723 
724     public long getLoopEndPoint() {
725         return loopEnd;
726     }
727 
728     public void setLoopCount(int count) {
729         if (count != LOOP_CONTINUOUSLY
730             && count < 0) {
731             throw new IllegalArgumentException("illegal value for loop count: "+count);
732         }
733         loopCount = count;
734         if (getDataPump() != null) {
735             getDataPump().resetLoopCount();
736         }
737     }
738 
739     public int getLoopCount() {
740         return loopCount;
741     }
742 
743 
744     /* *********************************** play control ************************* */
745 
746     /*
747      */
748     protected void implOpen() throws MidiUnavailableException {
749         if (Printer.trace) Printer.trace(">> RealTimeSequencer: implOpen()");
750 
751         //openInternalSynth();
752 
753         // create PlayThread
754         playThread = new PlayThread();
755 
756         //id = nOpen();
757         //if (id == 0) {
758         //    throw new MidiUnavailableException("unable to open sequencer");
759         //}
760         if (sequence != null) {
761             playThread.setSequence(sequence);
762         }
763 
764         // propagate caches
765         propagateCaches();
766 
767         if (doAutoConnectAtNextOpen) {
768             doAutoConnect();
769         }
770         if (Printer.trace) Printer.trace("<< RealTimeSequencer: implOpen() succeeded");
771     }
772 
773     private void doAutoConnect() {
774         if (Printer.trace) Printer.trace(">> RealTimeSequencer: doAutoConnect()");
775         Receiver rec = null;
776         // first try to connect to the default synthesizer
777         // IMPORTANT: this code needs to be synch'ed with
778         //            MidiSystem.getSequencer(boolean), because the same
779         //            algorithm needs to be used!
780         try {
781             Synthesizer synth = MidiSystem.getSynthesizer();
782             if (synth instanceof ReferenceCountingDevice) {
783                 rec = ((ReferenceCountingDevice) synth).getReceiverReferenceCounting();
784             } else {
785                 synth.open();
786                 try {
787                     rec = synth.getReceiver();
788                 } finally {
789                     // make sure that the synth is properly closed
790                     if (rec == null) {
791                         synth.close();
792                     }
793                 }
794             }
795         } catch (Exception e) {
796             // something went wrong with synth
797         }
798         if (rec == null) {
799             // then try to connect to the default Receiver
800             try {
801                 rec = MidiSystem.getReceiver();
802             } catch (Exception e) {
803                 // something went wrong. Nothing to do then!
804             }
805         }
806         if (rec != null) {
807             autoConnectedReceiver = rec;
808             try {
809                 getTransmitter().setReceiver(rec);
810             } catch (Exception e) {}
811         }
812         if (Printer.trace) Printer.trace("<< RealTimeSequencer: doAutoConnect() succeeded");
813     }
814 
815     private synchronized void propagateCaches() {
816         // only set caches if open and sequence is set
817         if (sequence != null && isOpen()) {
818             if (cacheTempoFactor != -1) {
819                 setTempoFactor(cacheTempoFactor);
820             }
821             if (cacheTempoMPQ == -1) {
822                 setTempoInMPQ((new MidiUtils.TempoCache(sequence)).getTempoMPQAt(getTickPosition()));
823             } else {
824                 setTempoInMPQ((float) cacheTempoMPQ);
825             }
826         }
827     }
828 
829     /** populate the caches with the current values */
830     private synchronized void setCaches() {
831         cacheTempoFactor = getTempoFactor();
832         cacheTempoMPQ = getTempoInMPQ();
833     }
834 
835 
836 
837     protected synchronized void implClose() {
838         if (Printer.trace) Printer.trace(">> RealTimeSequencer: implClose() ");
839 
840         if (playThread == null) {
841             if (Printer.err) Printer.err("RealTimeSequencer.implClose() called, but playThread not instanciated!");
842         } else {
843             // Interrupt playback loop.
844             playThread.close();
845             playThread = null;
846         }
847 
848         super.implClose();
849 
850         sequence = null;
851         running = false;
852         cacheTempoMPQ = -1;
853         cacheTempoFactor = -1;
854         trackMuted = null;
855         trackSolo = null;
856         loopStart = 0;
857         loopEnd = -1;
858         loopCount = 0;
859 
860         /** if this sequencer is set to autoconnect, need to
861          * re-establish the connection at next open!
862          */
863         doAutoConnectAtNextOpen = autoConnect;
864 
865         if (autoConnectedReceiver != null) {
866             try {
867                 autoConnectedReceiver.close();
868             } catch (Exception e) {}
869             autoConnectedReceiver = null;
870         }
871 
872         if (Printer.trace) Printer.trace("<< RealTimeSequencer: implClose() completed");
873     }
874 
875     protected void implStart() {
876         if (Printer.trace) Printer.trace(">> RealTimeSequencer: implStart()");
877 
878         if (playThread == null) {
879             if (Printer.err) Printer.err("RealTimeSequencer.implStart() called, but playThread not instanciated!");
880             return;
881         }
882 
883         tempoCache.refresh(sequence);
884         if (!running) {
885             running  = true;
886             playThread.start();
887         }
888         if (Printer.trace) Printer.trace("<< RealTimeSequencer: implStart() completed");
889     }
890 
891 
892     protected void implStop() {
893         if (Printer.trace) Printer.trace(">> RealTimeSequencer: implStop()");
894 
895         if (playThread == null) {
896             if (Printer.err) Printer.err("RealTimeSequencer.implStop() called, but playThread not instanciated!");
897             return;
898         }
899 
900         recording = false;
901         if (running) {
902             running = false;
903             playThread.stop();
904         }
905         if (Printer.trace) Printer.trace("<< RealTimeSequencer: implStop() completed");
906     }
907 
908 
909     /**
910      * Send midi player events.
911      * must not be synchronized on "this"
912      */
913     protected void sendMetaEvents(MidiMessage message) {
914         if (metaEventListeners.size() == 0) return;
915 
916         //if (Printer.debug) Printer.debug("sending a meta event");
917         eventDispatcher.sendAudioEvents(message, metaEventListeners);
918     }
919 
920     /**
921      * Send midi player events.
922      */
923     protected void sendControllerEvents(MidiMessage message) {
924         int size = controllerEventListeners.size();
925         if (size == 0) return;
926 
927         //if (Printer.debug) Printer.debug("sending a controller event");
928 
929         if (! (message instanceof ShortMessage)) {
930             if (Printer.debug) Printer.debug("sendControllerEvents: message is NOT instanceof ShortMessage!");
931             return;
932         }
933         ShortMessage msg = (ShortMessage) message;
934         int controller = msg.getData1();
935         List sendToListeners = new ArrayList();
936         for (int i = 0; i < size; i++) {
937             ControllerListElement cve = (ControllerListElement) controllerEventListeners.get(i);
938             for(int j = 0; j < cve.controllers.length; j++) {
939                 if (cve.controllers[j] == controller) {
940                     sendToListeners.add(cve.listener);
941                     break;
942                 }
943             }
944         }
945         eventDispatcher.sendAudioEvents(message, sendToListeners);
946     }
947 
948 
949 
950     private boolean needCaching() {
951         return !isOpen() || (sequence == null) || (playThread == null);
952     }
953 
954     /**
955      * return the data pump instance, owned by play thread
956      * if playthread is null, return null.
957      * This method is guaranteed to return non-null if
958      * needCaching returns false
959      */
960     private DataPump getDataPump() {
961         if (playThread != null) {
962             return playThread.getDataPump();
963         }
964         return null;
965     }
966 
967     private MidiUtils.TempoCache getTempoCache() {
968         return tempoCache;
969     }
970 
971     private static boolean[] ensureBoolArraySize(boolean[] array, int desiredSize) {
972         if (array == null) {
973             return new boolean[desiredSize];
974         }
975         if (array.length < desiredSize) {
976             boolean[] newArray = new boolean[desiredSize];
977             System.arraycopy(array, 0, newArray, 0, array.length);
978             return newArray;
979         }
980         return array;
981     }
982 
983 
984     // OVERRIDES OF ABSTRACT MIDI DEVICE METHODS
985 
986     protected boolean hasReceivers() {
987         return true;
988     }
989 
990     // for recording
991     protected Receiver createReceiver() throws MidiUnavailableException {
992         return new SequencerReceiver();
993     }
994 
995 
996     protected boolean hasTransmitters() {
997         return true;
998     }
999 
1000 
1001     protected Transmitter createTransmitter() throws MidiUnavailableException {
1002         return new SequencerTransmitter();
1003     }
1004 
1005 
1006     // interface AutoConnectSequencer
1007     public void setAutoConnect(Receiver autoConnectedReceiver) {
1008         this.autoConnect = (autoConnectedReceiver != null);
1009         this.autoConnectedReceiver = autoConnectedReceiver;
1010     }
1011 
1012 
1013 
1014     // INNER CLASSES
1015 
1016     /**
1017      * An own class to distinguish the class name from
1018      * the transmitter of other devices
1019      */
1020     private class SequencerTransmitter extends BasicTransmitter {
1021         private SequencerTransmitter() {
1022             super();
1023         }
1024     }
1025 
1026 
1027     class SequencerReceiver extends AbstractReceiver {
1028 
1029         protected void implSend(MidiMessage message, long timeStamp) {
1030             if (recording) {
1031                 long tickPos = 0;
1032 
1033                 // convert timeStamp to ticks
1034                 if (timeStamp < 0) {
1035                     tickPos = getTickPosition();
1036                 } else {
1037                     synchronized(tempoCache) {
1038                         tickPos = MidiUtils.microsecond2tick(sequence, timeStamp, tempoCache);
1039                     }
1040                 }
1041 
1042                 // and record to the first matching Track
1043                 Track track = null;
1044                 // do not record real-time events
1045                 // see 5048381: NullPointerException when saving a MIDI sequence
1046                 if (message.getLength() > 1) {
1047                     if (message instanceof ShortMessage) {
1048                         ShortMessage sm = (ShortMessage) message;
1049                         // all real-time messages have 0xF in the high nibble of the status byte
1050                         if ((sm.getStatus() & 0xF0) != 0xF0) {
1051                             track = RecordingTrack.get(recordingTracks, sm.getChannel());
1052                         }
1053                     } else {
1054                         // $$jb: where to record meta, sysex events?
1055                         // $$fb: the first recording track
1056                         track = RecordingTrack.get(recordingTracks, -1);
1057                     }
1058                     if (track != null) {
1059                         // create a copy of this message
1060                         if (message instanceof ShortMessage) {
1061                             message = new FastShortMessage((ShortMessage) message);
1062                         } else {
1063                             message = (MidiMessage) message.clone();
1064                         }
1065 
1066                         // create new MidiEvent
1067                         MidiEvent me = new MidiEvent(message, tickPos);
1068                         track.add(me);
1069                     }
1070                 }
1071             }
1072         }
1073     }
1074 
1075 
1076     private static class RealTimeSequencerInfo extends MidiDevice.Info {
1077 
1078         private static final String name = "Real Time Sequencer";
1079         private static final String vendor = "Oracle Corporation";
1080         private static final String description = "Software sequencer";
1081         private static final String version = "Version 1.0";
1082 
1083         private RealTimeSequencerInfo() {
1084             super(name, vendor, description, version);
1085         }
1086     } // class Info
1087 
1088 
1089     private class ControllerListElement {
1090 
1091         // $$jb: using an array for controllers b/c its
1092         //       easier to deal with than turning all the
1093         //       ints into objects to use a Vector
1094         int []  controllers;
1095         ControllerEventListener listener;
1096 
1097         private ControllerListElement(ControllerEventListener listener, int[] controllers) {
1098 
1099             this.listener = listener;
1100             if (controllers == null) {
1101                 controllers = new int[128];
1102                 for (int i = 0; i < 128; i++) {
1103                     controllers[i] = i;
1104                 }
1105             }
1106             this.controllers = controllers;
1107         }
1108 
1109         private void addControllers(int[] c) {
1110 
1111             if (c==null) {
1112                 controllers = new int[128];
1113                 for (int i = 0; i < 128; i++) {
1114                     controllers[i] = i;
1115                 }
1116                 return;
1117             }
1118             int temp[] = new int[ controllers.length + c.length ];
1119             int elements;
1120 
1121             // first add what we have
1122             for(int i=0; i<controllers.length; i++) {
1123                 temp[i] = controllers[i];
1124             }
1125             elements = controllers.length;
1126             // now add the new controllers only if we don't already have them
1127             for(int i=0; i<c.length; i++) {
1128                 boolean flag = false;
1129 
1130                 for(int j=0; j<controllers.length; j++) {
1131                     if (c[i] == controllers[j]) {
1132                         flag = true;
1133                         break;
1134                     }
1135                 }
1136                 if (!flag) {
1137                     temp[elements++] = c[i];
1138                 }
1139             }
1140             // now keep only the elements we need
1141             int newc[] = new int[ elements ];
1142             for(int i=0; i<elements; i++){
1143                 newc[i] = temp[i];
1144             }
1145             controllers = newc;
1146         }
1147 
1148         private void removeControllers(int[] c) {
1149 
1150             if (c==null) {
1151                 controllers = new int[0];
1152             } else {
1153                 int temp[] = new int[ controllers.length ];
1154                 int elements = 0;
1155 
1156 
1157                 for(int i=0; i<controllers.length; i++){
1158                     boolean flag = false;
1159                     for(int j=0; j<c.length; j++) {
1160                         if (controllers[i] == c[j]) {
1161                             flag = true;
1162                             break;
1163                         }
1164                     }
1165                     if (!flag){
1166                         temp[elements++] = controllers[i];
1167                     }
1168                 }
1169                 // now keep only the elements remaining
1170                 int newc[] = new int[ elements ];
1171                 for(int i=0; i<elements; i++) {
1172                     newc[i] = temp[i];
1173                 }
1174                 controllers = newc;
1175 
1176             }
1177         }
1178 
1179         private int[] getControllers() {
1180 
1181             // return a copy of our array of controllers,
1182             // so others can't mess with it
1183             if (controllers == null) {
1184                 return null;
1185             }
1186 
1187             int c[] = new int[controllers.length];
1188 
1189             for(int i=0; i<controllers.length; i++){
1190                 c[i] = controllers[i];
1191             }
1192             return c;
1193         }
1194 
1195     } // class ControllerListElement
1196 
1197 
1198     static class RecordingTrack {
1199 
1200         private Track track;
1201         private int channel;
1202 
1203         RecordingTrack(Track track, int channel) {
1204             this.track = track;
1205             this.channel = channel;
1206         }
1207 
1208         static RecordingTrack get(List recordingTracks, Track track) {
1209 
1210             synchronized(recordingTracks) {
1211                 int size = recordingTracks.size();
1212 
1213                 for (int i = 0; i < size; i++) {
1214                     RecordingTrack current = (RecordingTrack)recordingTracks.get(i);
1215                     if (current.track == track) {
1216                         return current;
1217                     }
1218                 }
1219             }
1220             return null;
1221         }
1222 
1223         static Track get(List recordingTracks, int channel) {
1224 
1225             synchronized(recordingTracks) {
1226                 int size = recordingTracks.size();
1227                 for (int i = 0; i < size; i++) {
1228                     RecordingTrack current = (RecordingTrack)recordingTracks.get(i);
1229                     if ((current.channel == channel) || (current.channel == -1)) {
1230                         return current.track;
1231                     }
1232                 }
1233             }
1234             return null;
1235 
1236         }
1237     }
1238 
1239 
1240     class PlayThread implements Runnable {
1241         private Thread thread;
1242         private Object lock = new Object();
1243 
1244         /** true if playback is interrupted (in close) */
1245         boolean interrupted = false;
1246         boolean isPumping = false;
1247 
1248         private DataPump dataPump = new DataPump();
1249 
1250 
1251         PlayThread() {
1252             // nearly MAX_PRIORITY
1253             int priority = Thread.NORM_PRIORITY
1254                 + ((Thread.MAX_PRIORITY - Thread.NORM_PRIORITY) * 3) / 4;
1255             thread = JSSecurityManager.createThread(this,
1256                                                     "Java Sound Sequencer", // name
1257                                                     false,                  // daemon
1258                                                     priority,               // priority
1259                                                     true);                  // doStart
1260         }
1261 
1262         DataPump getDataPump() {
1263             return dataPump;
1264         }
1265 
1266         synchronized void setSequence(Sequence seq) {
1267             dataPump.setSequence(seq);
1268         }
1269 
1270 
1271         /** start thread and pump. Requires up-to-date tempoCache */
1272         synchronized void start() {
1273             // mark the sequencer running
1274             running = true;
1275 
1276             if (!dataPump.hasCachedTempo()) {
1277                 long tickPos = getTickPosition();
1278                 dataPump.setTempoMPQ(tempoCache.getTempoMPQAt(tickPos));
1279             }
1280             dataPump.checkPointMillis = 0; // means restarted
1281             dataPump.clearNoteOnCache();
1282             dataPump.needReindex = true;
1283 
1284             dataPump.resetLoopCount();
1285 
1286             // notify the thread
1287             synchronized(lock) {
1288                 lock.notifyAll();
1289             }
1290 
1291             if (Printer.debug) Printer.debug(" ->Started MIDI play thread");
1292 
1293         }
1294 
1295         // waits until stopped
1296         synchronized void stop() {
1297             playThreadImplStop();
1298             long t = System.nanoTime() / 1000000l;
1299             while (isPumping) {
1300                 synchronized(lock) {
1301                     try {
1302                         lock.wait(2000);
1303                     } catch (InterruptedException ie) {
1304                         // ignore
1305                     }
1306                 }
1307                 // don't wait for more than 2 seconds
1308                 if ((System.nanoTime()/1000000l) - t > 1900) {
1309                     if (Printer.err) Printer.err("Waited more than 2 seconds in RealTimeSequencer.PlayThread.stop()!");
1310                     //break;
1311                 }
1312             }
1313         }
1314 
1315         void playThreadImplStop() {
1316             // mark the sequencer running
1317             running = false;
1318             synchronized(lock) {
1319                 lock.notifyAll();
1320             }
1321         }
1322 
1323         void close() {
1324             Thread oldThread = null;
1325             synchronized (this) {
1326                 // dispose of thread
1327                 interrupted = true;
1328                 oldThread = thread;
1329                 thread = null;
1330             }
1331             if (oldThread != null) {
1332                 // wake up the thread if it's in wait()
1333                 synchronized(lock) {
1334                     lock.notifyAll();
1335                 }
1336             }
1337             // wait for the thread to terminate itself,
1338             // but max. 2 seconds. Must not be synchronized!
1339             if (oldThread != null) {
1340                 try {
1341                     oldThread.join(2000);
1342                 } catch (InterruptedException ie) {}
1343             }
1344         }
1345 
1346 
1347         /**
1348          * Main process loop driving the media flow.
1349          *
1350          * Make sure to NOT synchronize on RealTimeSequencer
1351          * anywhere here (even implicit). That is a sure deadlock!
1352          */
1353         public void run() {
1354 
1355             while (!interrupted) {
1356                 boolean EOM = false;
1357                 boolean wasRunning = running;
1358                 isPumping = !interrupted && running;
1359                 while (!EOM && !interrupted && running) {
1360                     EOM = dataPump.pump();
1361 
1362                     try {
1363                         Thread.sleep(1);
1364                     } catch (InterruptedException ie) {
1365                         // ignore
1366                     }
1367                 }
1368                 if (Printer.debug) {
1369                     Printer.debug("Exited main pump loop because: ");
1370                     if (EOM) Printer.debug(" -> EOM is reached");
1371                     if (!running) Printer.debug(" -> running was set to false");
1372                     if (interrupted) Printer.debug(" -> interrupted was set to true");
1373                 }
1374 
1375                 playThreadImplStop();
1376                 if (wasRunning) {
1377                     dataPump.notesOff(true);
1378                 }
1379                 if (EOM) {
1380                     dataPump.setTickPos(sequence.getTickLength());
1381 
1382                     // send EOT event (mis-used for end of media)
1383                     MetaMessage message = new MetaMessage();
1384                     try{
1385                         message.setMessage(MidiUtils.META_END_OF_TRACK_TYPE, new byte[0], 0);
1386                     } catch(InvalidMidiDataException e1) {}
1387                     sendMetaEvents(message);
1388                 }
1389                 synchronized (lock) {
1390                     isPumping = false;
1391                     // wake up a waiting stop() method
1392                     lock.notifyAll();
1393                     while (!running && !interrupted) {
1394                         try {
1395                             lock.wait();
1396                         } catch (Exception ex) {}
1397                     }
1398                 }
1399             } // end of while(!EOM && !interrupted && running)
1400             if (Printer.debug) Printer.debug("end of play thread");
1401         }
1402     }
1403 
1404 
1405     /**
1406      * class that does the actual dispatching of events,
1407      * used to be in native in MMAPI
1408      */
1409     private class DataPump {
1410         private float currTempo;         // MPQ tempo
1411         private float tempoFactor;       // 1.0 is default
1412         private float inverseTempoFactor;// = 1.0 / tempoFactor
1413         private long ignoreTempoEventAt; // ignore next META tempo during playback at this tick pos only
1414         private int resolution;
1415         private float divisionType;
1416         private long checkPointMillis;   // microseconds at checkoint
1417         private long checkPointTick;     // ticks at checkpoint
1418         private int[] noteOnCache;       // bit-mask of notes that are currently on
1419         private Track[] tracks;
1420         private boolean[] trackDisabled; // if true, do not play this track
1421         private int[] trackReadPos;      // read index per track
1422         private long lastTick;
1423         private boolean needReindex = false;
1424         private int currLoopCounter = 0;
1425 
1426         //private sun.misc.Perf perf = sun.misc.Perf.getPerf();
1427         //private long perfFreq = perf.highResFrequency();
1428 
1429 
1430         DataPump() {
1431             init();
1432         }
1433 
1434         synchronized void init() {
1435             ignoreTempoEventAt = -1;
1436             tempoFactor = 1.0f;
1437             inverseTempoFactor = 1.0f;
1438             noteOnCache = new int[128];
1439             tracks = null;
1440             trackDisabled = null;
1441         }
1442 
1443         synchronized void setTickPos(long tickPos) {
1444             long oldLastTick = tickPos;
1445             lastTick = tickPos;
1446             if (running) {
1447                 notesOff(false);
1448             }
1449             if (running || tickPos > 0) {
1450                 // will also reindex
1451                 chaseEvents(oldLastTick, tickPos);
1452             } else {
1453                 needReindex = true;
1454             }
1455             if (!hasCachedTempo()) {
1456                 setTempoMPQ(getTempoCache().getTempoMPQAt(lastTick, currTempo));
1457                 // treat this as if it is a real time tempo change
1458                 ignoreTempoEventAt = -1;
1459             }
1460             // trigger re-configuration
1461             checkPointMillis = 0;
1462         }
1463 
1464         long getTickPos() {
1465             return lastTick;
1466         }
1467 
1468         // hasCachedTempo is only valid if it is the current position
1469         boolean hasCachedTempo() {
1470             if (ignoreTempoEventAt != lastTick) {
1471                 ignoreTempoEventAt = -1;
1472             }
1473             return ignoreTempoEventAt >= 0;
1474         }
1475 
1476         // this method is also used internally in the pump!
1477         synchronized void setTempoMPQ(float tempoMPQ) {
1478             if (tempoMPQ > 0 && tempoMPQ != currTempo) {
1479                 ignoreTempoEventAt = lastTick;
1480                 this.currTempo = tempoMPQ;
1481                 // re-calculate check point
1482                 checkPointMillis = 0;
1483             }
1484         }
1485 
1486         float getTempoMPQ() {
1487             return currTempo;
1488         }
1489 
1490         synchronized void setTempoFactor(float factor) {
1491             if (factor > 0 && factor != this.tempoFactor) {
1492                 tempoFactor = factor;
1493                 inverseTempoFactor = 1.0f / factor;
1494                 // re-calculate check point
1495                 checkPointMillis = 0;
1496             }
1497         }
1498 
1499         float getTempoFactor() {
1500             return tempoFactor;
1501         }
1502 
1503         synchronized void muteSoloChanged() {
1504             boolean[] newDisabled = makeDisabledArray();
1505             if (running) {
1506                 applyDisabledTracks(trackDisabled, newDisabled);
1507             }
1508             trackDisabled = newDisabled;
1509         }
1510 
1511 
1512 
1513         synchronized void setSequence(Sequence seq) {
1514             if (seq == null) {
1515                 init();
1516                 return;
1517             }
1518             tracks = seq.getTracks();
1519             muteSoloChanged();
1520             resolution = seq.getResolution();
1521             divisionType = seq.getDivisionType();
1522             trackReadPos = new int[tracks.length];
1523             // trigger re-initialization
1524             checkPointMillis = 0;
1525             needReindex = true;
1526         }
1527 
1528         synchronized void resetLoopCount() {
1529             currLoopCounter = loopCount;
1530         }
1531 
1532         void clearNoteOnCache() {
1533             for (int i = 0; i < 128; i++) {
1534                 noteOnCache[i] = 0;
1535             }
1536         }
1537 
1538         void notesOff(boolean doControllers) {
1539             int done = 0;
1540             for (int ch=0; ch<16; ch++) {
1541                 int channelMask = (1<<ch);
1542                 for (int i=0; i<128; i++) {
1543                     if ((noteOnCache[i] & channelMask) != 0) {
1544                         noteOnCache[i] ^= channelMask;
1545                         // send note on with velocity 0
1546                         getTransmitterList().sendMessage((ShortMessage.NOTE_ON | ch) | (i<<8), -1);
1547                         done++;
1548                     }
1549                 }
1550                 /* all notes off */
1551                 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (123<<8), -1);
1552                 /* sustain off */
1553                 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (64<<8), -1);
1554                 if (doControllers) {
1555                     /* reset all controllers */
1556                     getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (121<<8), -1);
1557                     done++;
1558                 }
1559             }
1560             if (DEBUG_PUMP) Printer.println("  noteOff: sent "+done+" messages.");
1561         }
1562 
1563 
1564         private boolean[] makeDisabledArray() {
1565             if (tracks == null) {
1566                 return null;
1567             }
1568             boolean[] newTrackDisabled = new boolean[tracks.length];
1569             boolean[] solo;
1570             boolean[] mute;
1571             synchronized(RealTimeSequencer.this) {
1572                 mute = trackMuted;
1573                 solo = trackSolo;
1574             }
1575             // if one track is solo, then only play solo
1576             boolean hasSolo = false;
1577             if (solo != null) {
1578                 for (int i = 0; i < solo.length; i++) {
1579                     if (solo[i]) {
1580                         hasSolo = true;
1581                         break;
1582                     }
1583                 }
1584             }
1585             if (hasSolo) {
1586                 // only the channels with solo play, regardless of mute
1587                 for (int i = 0; i < newTrackDisabled.length; i++) {
1588                     newTrackDisabled[i] = (i >= solo.length) || (!solo[i]);
1589                 }
1590             } else {
1591                 // mute the selected channels
1592                 for (int i = 0; i < newTrackDisabled.length; i++) {
1593                     newTrackDisabled[i] = (mute != null) && (i < mute.length) && (mute[i]);
1594                 }
1595             }
1596             return newTrackDisabled;
1597         }
1598 
1599         /**
1600          * chase all events from beginning of Track
1601          * and send note off for those events that are active
1602          * in noteOnCache array.
1603          * It is possible, of course, to catch notes from other tracks,
1604          * but better than more complicated logic to detect
1605          * which notes are really from this track
1606          */
1607         private void sendNoteOffIfOn(Track track, long endTick) {
1608             int size = track.size();
1609             int done = 0;
1610             try {
1611                 for (int i = 0; i < size; i++) {
1612                     MidiEvent event = track.get(i);
1613                     if (event.getTick() > endTick) break;
1614                     MidiMessage msg = event.getMessage();
1615                     int status = msg.getStatus();
1616                     int len = msg.getLength();
1617                     if (len == 3 && ((status & 0xF0) == ShortMessage.NOTE_ON)) {
1618                         int note = -1;
1619                         if (msg instanceof ShortMessage) {
1620                             ShortMessage smsg = (ShortMessage) msg;
1621                             if (smsg.getData2() > 0) {
1622                                 // only consider Note On with velocity > 0
1623                                 note = smsg.getData1();
1624                             }
1625                         } else {
1626                             byte[] data = msg.getMessage();
1627                             if ((data[2] & 0x7F) > 0) {
1628                                 // only consider Note On with velocity > 0
1629                                 note = data[1] & 0x7F;
1630                             }
1631                         }
1632                         if (note >= 0) {
1633                             int bit = 1<<(status & 0x0F);
1634                             if ((noteOnCache[note] & bit) != 0) {
1635                                 // the bit is set. Send Note Off
1636                                 getTransmitterList().sendMessage(status | (note<<8), -1);
1637                                 // clear the bit
1638                                 noteOnCache[note] &= (0xFFFF ^ bit);
1639                                 done++;
1640                             }
1641                         }
1642                     }
1643                 }
1644             } catch (ArrayIndexOutOfBoundsException aioobe) {
1645                 // this happens when messages are removed
1646                 // from the track while this method executes
1647             }
1648             if (DEBUG_PUMP) Printer.println("  sendNoteOffIfOn: sent "+done+" messages.");
1649         }
1650 
1651 
1652         /**
1653          * Runtime application of mute/solo:
1654          * if a track is muted that was previously playing, send
1655          *    note off events for all currently playing notes
1656          */
1657         private void applyDisabledTracks(boolean[] oldDisabled, boolean[] newDisabled) {
1658             byte[][] tempArray = null;
1659             synchronized(RealTimeSequencer.this) {
1660                 for (int i = 0; i < newDisabled.length; i++) {
1661                     if (((oldDisabled == null)
1662                          || (i >= oldDisabled.length)
1663                          || !oldDisabled[i])
1664                         && newDisabled[i]) {
1665                         // case that a track gets muted: need to
1666                         // send appropriate note off events to prevent
1667                         // hanging notes
1668 
1669                         if (tracks.length > i) {
1670                             sendNoteOffIfOn(tracks[i], lastTick);
1671                         }
1672                     }
1673                     else if ((oldDisabled != null)
1674                              && (i < oldDisabled.length)
1675                              && oldDisabled[i]
1676                              && !newDisabled[i]) {
1677                         // case that a track was muted and is now unmuted
1678                         // need to chase events and re-index this track
1679                         if (tempArray == null) {
1680                             tempArray = new byte[128][16];
1681                         }
1682                         chaseTrackEvents(i, 0, lastTick, true, tempArray);
1683                     }
1684                 }
1685             }
1686         }
1687 
1688         /** go through all events from startTick to endTick
1689          * chase the controller state and program change state
1690          * and then set the end-states at once.
1691          *
1692          * needs to be called in synchronized state
1693          * @param tempArray an byte[128][16] to hold controller messages
1694          */
1695         private void chaseTrackEvents(int trackNum,
1696                                       long startTick,
1697                                       long endTick,
1698                                       boolean doReindex,
1699                                       byte[][] tempArray) {
1700             if (startTick > endTick) {
1701                 // start from the beginning
1702                 startTick = 0;
1703             }
1704             byte[] progs = new byte[16];
1705             // init temp array with impossible values
1706             for (int ch = 0; ch < 16; ch++) {
1707                 progs[ch] = -1;
1708                 for (int co = 0; co < 128; co++) {
1709                     tempArray[co][ch] = -1;
1710                 }
1711             }
1712             Track track = tracks[trackNum];
1713             int size = track.size();
1714             try {
1715                 for (int i = 0; i < size; i++) {
1716                     MidiEvent event = track.get(i);
1717                     if (event.getTick() >= endTick) {
1718                         if (doReindex && (trackNum < trackReadPos.length)) {
1719                             trackReadPos[trackNum] = (i > 0)?(i-1):0;
1720                             if (DEBUG_PUMP) Printer.println("  chaseEvents: setting trackReadPos["+trackNum+"] = "+trackReadPos[trackNum]);
1721                         }
1722                         break;
1723                     }
1724                     MidiMessage msg = event.getMessage();
1725                     int status = msg.getStatus();
1726                     int len = msg.getLength();
1727                     if (len == 3 && ((status & 0xF0) == ShortMessage.CONTROL_CHANGE)) {
1728                         if (msg instanceof ShortMessage) {
1729                             ShortMessage smsg = (ShortMessage) msg;
1730                             tempArray[smsg.getData1() & 0x7F][status & 0x0F] = (byte) smsg.getData2();
1731                         } else {
1732                             byte[] data = msg.getMessage();
1733                             tempArray[data[1] & 0x7F][status & 0x0F] = data[2];
1734                         }
1735                     }
1736                     if (len == 2 && ((status & 0xF0) == ShortMessage.PROGRAM_CHANGE)) {
1737                         if (msg instanceof ShortMessage) {
1738                             ShortMessage smsg = (ShortMessage) msg;
1739                             progs[status & 0x0F] = (byte) smsg.getData1();
1740                         } else {
1741                             byte[] data = msg.getMessage();
1742                             progs[status & 0x0F] = data[1];
1743                         }
1744                     }
1745                 }
1746             } catch (ArrayIndexOutOfBoundsException aioobe) {
1747                 // this happens when messages are removed
1748                 // from the track while this method executes
1749             }
1750             int numControllersSent = 0;
1751             // now send out the aggregated controllers and program changes
1752             for (int ch = 0; ch < 16; ch++) {
1753                 for (int co = 0; co < 128; co++) {
1754                     byte controllerValue = tempArray[co][ch];
1755                     if (controllerValue >= 0) {
1756                         int packedMsg = (ShortMessage.CONTROL_CHANGE | ch) | (co<<8) | (controllerValue<<16);
1757                         getTransmitterList().sendMessage(packedMsg, -1);
1758                         numControllersSent++;
1759                     }
1760                 }
1761                 // send program change *after* controllers, to
1762                 // correctly initialize banks
1763                 if (progs[ch] >= 0) {
1764                     getTransmitterList().sendMessage((ShortMessage.PROGRAM_CHANGE | ch) | (progs[ch]<<8), -1);
1765                 }
1766                 if (progs[ch] >= 0 || startTick == 0 || endTick == 0) {
1767                     // reset pitch bend on this channel (E0 00 40)
1768                     getTransmitterList().sendMessage((ShortMessage.PITCH_BEND | ch) | (0x40 << 16), -1);
1769                     // reset sustain pedal on this channel
1770                     getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (64 << 8), -1);
1771                 }
1772             }
1773             if (DEBUG_PUMP) Printer.println("  chaseTrackEvents track "+trackNum+": sent "+numControllersSent+" controllers.");
1774         }
1775 
1776 
1777         /** chase controllers and program for all tracks */
1778         synchronized void chaseEvents(long startTick, long endTick) {
1779             if (DEBUG_PUMP) Printer.println(">> chaseEvents from tick "+startTick+".."+(endTick-1));
1780             byte[][] tempArray = new byte[128][16];
1781             for (int t = 0; t < tracks.length; t++) {
1782                 if ((trackDisabled == null)
1783                     || (trackDisabled.length <= t)
1784                     || (!trackDisabled[t])) {
1785                     // if track is not disabled, chase the events for it
1786                     chaseTrackEvents(t, startTick, endTick, true, tempArray);
1787                 }
1788             }
1789             if (DEBUG_PUMP) Printer.println("<< chaseEvents");
1790         }
1791 
1792 
1793         // playback related methods (pumping)
1794 
1795         private long getCurrentTimeMillis() {
1796             return System.nanoTime() / 1000000l;
1797             //return perf.highResCounter() * 1000 / perfFreq;
1798         }
1799 
1800         private long millis2tick(long millis) {
1801             if (divisionType != Sequence.PPQ) {
1802                 double dTick = ((((double) millis) * tempoFactor)
1803                                 * ((double) divisionType)
1804                                 * ((double) resolution))
1805                     / ((double) 1000);
1806                 return (long) dTick;
1807             }
1808             return MidiUtils.microsec2ticks(millis * 1000,
1809                                             currTempo * inverseTempoFactor,
1810                                             resolution);
1811         }
1812 
1813         private long tick2millis(long tick) {
1814             if (divisionType != Sequence.PPQ) {
1815                 double dMillis = ((((double) tick) * 1000) /
1816                                   (tempoFactor * ((double) divisionType) * ((double) resolution)));
1817                 return (long) dMillis;
1818             }
1819             return MidiUtils.ticks2microsec(tick,
1820                                             currTempo * inverseTempoFactor,
1821                                             resolution) / 1000;
1822         }
1823 
1824         private void ReindexTrack(int trackNum, long tick) {
1825             if (trackNum < trackReadPos.length && trackNum < tracks.length) {
1826                 trackReadPos[trackNum] = MidiUtils.tick2index(tracks[trackNum], tick);
1827                 if (DEBUG_PUMP) Printer.println("  reindexTrack: setting trackReadPos["+trackNum+"] = "+trackReadPos[trackNum]);
1828             }
1829         }
1830 
1831         /* returns if changes are pending */
1832         private boolean dispatchMessage(int trackNum, MidiEvent event) {
1833             boolean changesPending = false;
1834             MidiMessage message = event.getMessage();
1835             int msgStatus = message.getStatus();
1836             int msgLen = message.getLength();
1837             if (msgStatus == MetaMessage.META && msgLen >= 2) {
1838                 // a meta message. Do not send it to the device.
1839                 // 0xFF with length=1 is a MIDI realtime message
1840                 // which shouldn't be in a Sequence, but we play it
1841                 // nonetheless.
1842 
1843                 // see if this is a tempo message. Only on track 0.
1844                 if (trackNum == 0) {
1845                     int newTempo = MidiUtils.getTempoMPQ(message);
1846                     if (newTempo > 0) {
1847                         if (event.getTick() != ignoreTempoEventAt) {
1848                             setTempoMPQ(newTempo); // sets ignoreTempoEventAt!
1849                             changesPending = true;
1850                         }
1851                         // next loop, do not ignore anymore tempo events.
1852                         ignoreTempoEventAt = -1;
1853                     }
1854                 }
1855                 // send to listeners
1856                 sendMetaEvents(message);
1857 
1858             } else {
1859                 // not meta, send to device
1860                 getTransmitterList().sendMessage(message, -1);
1861 
1862                 switch (msgStatus & 0xF0) {
1863                 case ShortMessage.NOTE_OFF: {
1864                     // note off - clear the bit in the noteOnCache array
1865                     int note = ((ShortMessage) message).getData1() & 0x7F;
1866                     noteOnCache[note] &= (0xFFFF ^ (1<<(msgStatus & 0x0F)));
1867                     break;
1868                 }
1869 
1870                 case ShortMessage.NOTE_ON: {
1871                     // note on
1872                     ShortMessage smsg = (ShortMessage) message;
1873                     int note = smsg.getData1() & 0x7F;
1874                     int vel = smsg.getData2() & 0x7F;
1875                     if (vel > 0) {
1876                         // if velocity > 0 set the bit in the noteOnCache array
1877                         noteOnCache[note] |= 1<<(msgStatus & 0x0F);
1878                     } else {
1879                         // if velocity = 0 clear the bit in the noteOnCache array
1880                         noteOnCache[note] &= (0xFFFF ^ (1<<(msgStatus & 0x0F)));
1881                     }
1882                     break;
1883                 }
1884 
1885                 case ShortMessage.CONTROL_CHANGE:
1886                     // if controller message, send controller listeners
1887                     sendControllerEvents(message);
1888                     break;
1889 
1890                 }
1891             }
1892             return changesPending;
1893         }
1894 
1895 
1896         /** the main pump method
1897          * @return true if end of sequence is reached
1898          */
1899         synchronized boolean pump() {
1900             long currMillis;
1901             long targetTick = lastTick;
1902             MidiEvent currEvent;
1903             boolean changesPending = false;
1904             boolean doLoop = false;
1905             boolean EOM = false;
1906 
1907             currMillis = getCurrentTimeMillis();
1908             int finishedTracks = 0;
1909             do {
1910                 changesPending = false;
1911 
1912                 // need to re-find indexes in tracks?
1913                 if (needReindex) {
1914                     if (DEBUG_PUMP) Printer.println("Need to re-index at "+currMillis+" millis. TargetTick="+targetTick);
1915                     if (trackReadPos.length < tracks.length) {
1916                         trackReadPos = new int[tracks.length];
1917                     }
1918                     for (int t = 0; t < tracks.length; t++) {
1919                         ReindexTrack(t, targetTick);
1920                         if (DEBUG_PUMP_ALL) Printer.println("  Setting trackReadPos["+t+"]="+trackReadPos[t]);
1921                     }
1922                     needReindex = false;
1923                     checkPointMillis = 0;
1924                 }
1925 
1926                 // get target tick from current time in millis
1927                 if (checkPointMillis == 0) {
1928                     // new check point
1929                     currMillis = getCurrentTimeMillis();
1930                     checkPointMillis = currMillis;
1931                     targetTick = lastTick;
1932                     checkPointTick = targetTick;
1933                     if (DEBUG_PUMP) Printer.println("New checkpoint to "+currMillis+" millis. "
1934                                                        +"TargetTick="+targetTick
1935                                                        +" new tempo="+MidiUtils.convertTempo(currTempo)+"bpm");
1936                 } else {
1937                     // calculate current tick based on current time in milliseconds
1938                     targetTick = checkPointTick + millis2tick(currMillis - checkPointMillis);
1939                     if (DEBUG_PUMP_ALL) Printer.println("targetTick = "+targetTick+" at "+currMillis+" millis");
1940                     if ((loopEnd != -1)
1941                         && ((loopCount > 0 && currLoopCounter > 0)
1942                             || (loopCount == LOOP_CONTINUOUSLY))) {
1943                         if (lastTick <= loopEnd && targetTick >= loopEnd) {
1944                             // need to loop!
1945                             // only play until loop end
1946                             targetTick = loopEnd - 1;
1947                             doLoop = true;
1948                             if (DEBUG_PUMP) Printer.println("set doLoop to true. lastTick="+lastTick
1949                                                                +"  targetTick="+targetTick
1950                                                                +"  loopEnd="+loopEnd
1951                                                                +"  jumping to loopStart="+loopStart
1952                                                                +"  new currLoopCounter="+currLoopCounter);
1953                             if (DEBUG_PUMP) Printer.println("  currMillis="+currMillis
1954                                                                +"  checkPointMillis="+checkPointMillis
1955                                                                +"  checkPointTick="+checkPointTick);
1956 
1957                         }
1958                     }
1959                     lastTick = targetTick;
1960                 }
1961 
1962                 finishedTracks = 0;
1963 
1964                 for (int t = 0; t < tracks.length; t++) {
1965                     try {
1966                         boolean disabled = trackDisabled[t];
1967                         Track thisTrack = tracks[t];
1968                         int readPos = trackReadPos[t];
1969                         int size = thisTrack.size();
1970                         // play all events that are due until targetTick
1971                         while (!changesPending && (readPos < size)
1972                                && (currEvent = thisTrack.get(readPos)).getTick() <= targetTick) {
1973 
1974                             if ((readPos == size -1) &&  MidiUtils.isMetaEndOfTrack(currEvent.getMessage())) {
1975                                 // do not send out this message. Finished with this track
1976                                 readPos = size;
1977                                 break;
1978                             }
1979                             // TODO: some kind of heuristics if the MIDI messages have changed
1980                             // significantly (i.e. deleted or inserted a bunch of messages)
1981                             // since last time. Would need to set needReindex = true then
1982                             readPos++;
1983                             // only play this event if the track is enabled,
1984                             // or if it is a tempo message on track 0
1985                             // Note: cannot put this check outside
1986                             //       this inner loop in order to detect end of file
1987                             if (!disabled ||
1988                                 ((t == 0) && (MidiUtils.isMetaTempo(currEvent.getMessage())))) {
1989                                 changesPending = dispatchMessage(t, currEvent);
1990                             }
1991                         }
1992                         if (readPos >= size) {
1993                             finishedTracks++;
1994                         }
1995                         if (DEBUG_PUMP_ALL) {
1996                             System.out.print(" pumped track "+t+" ("+size+" events) "
1997                                              +" from index: "+trackReadPos[t]
1998                                              +" to "+(readPos-1));
1999                             System.out.print(" -> ticks: ");
2000                             if (trackReadPos[t] < size) {
2001                                 System.out.print(""+(thisTrack.get(trackReadPos[t]).getTick()));
2002                             } else {
2003                                 System.out.print("EOT");
2004                             }
2005                             System.out.print(" to ");
2006                             if (readPos < size) {
2007                                 System.out.print(""+(thisTrack.get(readPos-1).getTick()));
2008                             } else {
2009                                 System.out.print("EOT");
2010                             }
2011                             System.out.println();
2012                         }
2013                         trackReadPos[t] = readPos;
2014                     } catch(Exception e) {
2015                         if (Printer.debug) Printer.debug("Exception in Sequencer pump!");
2016                         if (Printer.debug) e.printStackTrace();
2017                         if (e instanceof ArrayIndexOutOfBoundsException) {
2018                             needReindex = true;
2019                             changesPending = true;
2020                         }
2021                     }
2022                     if (changesPending) {
2023                         break;
2024                     }
2025                 }
2026                 EOM = (finishedTracks == tracks.length);
2027                 if (doLoop
2028                     || ( ((loopCount > 0 && currLoopCounter > 0)
2029                           || (loopCount == LOOP_CONTINUOUSLY))
2030                          && !changesPending
2031                          && (loopEnd == -1)
2032                          && EOM)) {
2033 
2034                     long oldCheckPointMillis = checkPointMillis;
2035                     long loopEndTick = loopEnd;
2036                     if (loopEndTick == -1) {
2037                         loopEndTick = lastTick;
2038                     }
2039 
2040                     // need to loop back!
2041                     if (loopCount != LOOP_CONTINUOUSLY) {
2042                         currLoopCounter--;
2043                     }
2044                     if (DEBUG_PUMP) Printer.println("Execute loop: lastTick="+lastTick
2045                                                        +"  loopEnd="+loopEnd
2046                                                        +"  jumping to loopStart="+loopStart
2047                                                        +"  new currLoopCounter="+currLoopCounter);
2048                     setTickPos(loopStart);
2049                     // now patch the checkPointMillis so that
2050                     // it points to the exact beginning of when the loop was finished
2051 
2052                     // $$fb TODO: although this is mathematically correct (i.e. the loop position
2053                     //            is correct, and doesn't drift away with several repetition,
2054                     //            there is a slight lag when looping back, probably caused
2055                     //            by the chasing.
2056 
2057                     checkPointMillis = oldCheckPointMillis + tick2millis(loopEndTick - checkPointTick);
2058                     checkPointTick = loopStart;
2059                     if (DEBUG_PUMP) Printer.println("  Setting currMillis="+currMillis
2060                                                        +"  new checkPointMillis="+checkPointMillis
2061                                                        +"  new checkPointTick="+checkPointTick);
2062                     // no need for reindexing, is done in setTickPos
2063                     needReindex = false;
2064                     changesPending = false;
2065                     // reset doLoop flag
2066                     doLoop = false;
2067                     EOM = false;
2068                 }
2069             } while (changesPending);
2070 
2071             return EOM;
2072         }
2073 
2074     } // class DataPump
2075 
2076 }